﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using StackExchange.Redis;

namespace RedisLib
{
    public partial class RedisClient
    {
        public async Task<bool> SetAdd<T>(string key, T value, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return false;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = ConvertObjectValueToRedisValue(value);

            return await database.SetAddAsync(redisKey, redisValue);
        }

        public async Task<long> SetAdd<T>(string key, List<T> values, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return -1;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = BatchConvertObjectValueToRedisValue(values);

            return await database.SetAddAsync(redisKey, redisValues);
        }

        public async Task<T[]> SetCombine<T>(SetOperation operation, List<string> keys, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey[] redisKeys = BatchConvertStringKeyToRedisKey(keys);
            RedisValue[] redisValues = await database.SetCombineAsync(operation, redisKeys);
            if (redisValues == null || redisValues.Length == 0) return [];

            T[] values = new T[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                values[i] = ConvertRedisValueToObjectValue<T>(redisValues[i]);
            }

            return values;
        }

        public async Task<T?[]> SetCombineForValueType<T>(SetOperation operation, List<string> keys, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey[] redisKeys = BatchConvertStringKeyToRedisKey(keys);
            RedisValue[] redisValues = await database.SetCombineAsync(operation, redisKeys);
            if (redisValues == null || redisValues.Length == 0) return [];

            T?[] values = new T?[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                values[i] = ConvertRedisValueToValueTypeValue<T>(redisValues[i]);
            }

            return values;
        }

        public async Task<bool> SetContains<T>(string key, T value, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return false;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = ConvertObjectValueToRedisValue(value);

            return await database.SetContainsAsync(redisKey, redisValue);
        }

        public async Task<long> SetLength(string key, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return -1;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);

            return await database.SetLengthAsync(redisKey);
        }

        public async Task<T[]> SetMembers<T>(string key, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = await database.SetMembersAsync(redisKey);

            T[] values = new T[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                values[i] = ConvertRedisValueToObjectValue<T>(redisValues[i]);
            }

            return values;
        }

        public async Task<T?[]> SetMembersForValueType<T>(string key, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = await database.SetMembersAsync(redisKey);

            T?[] values = new T?[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                values[i] = ConvertRedisValueToValueTypeValue<T>(redisValues[i]);
            }

            return values;
        }

        public async Task<T> SetPop<T>(string key, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = await database.SetPopAsync(redisKey);

            return ConvertRedisValueToObjectValue<T>(redisValue);
        }

        public async Task<T[]> SetPop<T>(string key, long count, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = await database.SetPopAsync(redisKey, count);

            T[] values = new T[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                values[i] = ConvertRedisValueToObjectValue<T>(redisValues[i]);
            }

            return values;
        }

        public async Task<T?> SetPopForValueType<T>(string key, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = await database.SetPopAsync(redisKey);

            return ConvertRedisValueToValueTypeValue<T>(redisValue);
        }

        public async Task<T?[]> SetPopForValueType<T>(string key, long count, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = await database.SetPopAsync(redisKey, count);

            T?[] values = new T?[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                values[i] = ConvertRedisValueToValueTypeValue<T>(redisValues[i]);
            }

            return values;
        }

        public async Task<T> SetRandomMember<T>(string key, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = await database.SetRandomMemberAsync(redisKey);

            return ConvertRedisValueToObjectValue<T>(redisValue);
        }

        public async Task<T[]> SetRandomMember<T>(string key, long count, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = await database.SetRandomMembersAsync(redisKey, count);

            T[] values = new T[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                values[i] = ConvertRedisValueToObjectValue<T>(redisValues[i]);
            }

            return values;
        }

        public async Task<T?> SetRandomMemberForValueType<T>(string key, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = await database.SetRandomMemberAsync(redisKey);

            return ConvertRedisValueToValueTypeValue<T>(redisValue);
        }

        public async Task<T?[]> SetRandomMemberForValueType<T>(string key, long count, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = await database.SetRandomMembersAsync(redisKey, count);

            T?[] values = new T?[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                values[i] = ConvertRedisValueToValueTypeValue<T>(redisValues[i]);
            }

            return values;
        }

        public async Task<bool> SetRemove<T>(string key, T value, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return false;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = ConvertObjectValueToRedisValue(value);

            return await database.SetRemoveAsync(redisKey, redisValue);
        }

        public async Task<long> SetRemove<T>(string key, List<T> values, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return -1;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = BatchConvertObjectValueToRedisValue(values);

            return await database.SetRemoveAsync(redisKey, redisValues);
        }

        public async IAsyncEnumerable<T> SetScan<T>(string key, string pattern = default, int pageSize = 250, long cursor = 0L, int pageOffset = 0, int db = -1, [EnumeratorCancellation] CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) yield break;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisPattern = ConvertObjectValueToRedisValue(pattern);

            await foreach (var redisValue in database.SetScanAsync(redisKey, redisPattern, pageSize, cursor, pageOffset))
            {
                yield return ConvertRedisValueToObjectValue<T>(redisValue);
            }
        }

        public async IAsyncEnumerable<T?> SetScanForValueType<T>(string key, string pattern = default, int pageSize = 250, long cursor = 0L, int pageOffset = 0, int db = -1, [EnumeratorCancellation] CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) yield break;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisPattern = ConvertObjectValueToRedisValue(pattern);

            await foreach (var redisValue in database.SetScanAsync(redisKey, redisPattern, pageSize, cursor, pageOffset))
            {
                yield return ConvertRedisValueToValueTypeValue<T>(redisValue);
            }
        }
    }
}
